package com.raven.craft.code.generator.project.antijson;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.*;
import com.raven.craft.code.generator.common.constant.Constant;
import com.raven.craft.code.generator.common.util.StringUtil;
import com.raven.craft.code.generator.project.antijson.type.*;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.util.Assert;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

/**
 * @author raven
 * @date 2020/09/19
 **/
@Getter
@Setter
@ToString
public class AntiJsonVo {

    private String targetPackage;
    private Map<String, ObjectType> typeMap = new LinkedHashMap<>();
    private List<ObjectType> objectTypes;
    private Set<String> imports;

    public AntiJsonVo(JsonNode jsonNode, AntiJsonProperties properties) {
        Assert.isTrue(isAbstractObject(jsonNode), "Illegal jsonNode");
        abstractJson("", properties.getRootTypeName(), jsonNode);
        this.objectTypes = typeMap.values().stream().collect(Collectors.toList());
        Collections.reverse(this.objectTypes);
        this.targetPackage = properties.getTargetPackage();

        this.imports = objectTypes.stream()
                .map(ObjectType::getImports)
                .flatMap(Collection::stream)
                .filter(s -> !s.startsWith("java.lang."))
                .collect(Collectors.toSet());
    }

    private Boolean isAbstractObject(JsonNode jsonNode) {
        if (jsonNode instanceof ObjectNode) return true;
        if (jsonNode instanceof ArrayNode && !jsonNode.isEmpty()) return isAbstractObject(jsonNode.get(0));
        return false;
    }

    private Type abstractJson(String parentNodeName, String nodeName, JsonNode jsonNode) {
        if (jsonNode instanceof NullNode) return NullType.getInstance();
        if (jsonNode instanceof IntNode) return IntegerType.getInstance();
        if (jsonNode instanceof LongNode) return LongType.getInstance();
        if (jsonNode instanceof DoubleNode) return DoubleType.getInstance();
        if (jsonNode instanceof DecimalNode) return BigDecimalType.getInstance();
        if (jsonNode instanceof BooleanNode) return BooleanType.getInstance();
        if (jsonNode instanceof TextNode) return StringType.getInstance();
        if (jsonNode instanceof ArrayNode) {
            return antiArray(generateQualifiedName(parentNodeName, nodeName), (ArrayNode) jsonNode);
        }
        if (jsonNode instanceof ObjectNode) {
            ObjectType objectType = antiObject(generateQualifiedName(parentNodeName, nodeName), (ObjectNode) jsonNode);
            // 将对象类型放入 typeMap
            ObjectType _objectType = typeMap.get(objectType.getQualifiedName());
            if (_objectType == null) {
                typeMap.put(objectType.getQualifiedName(), objectType);
            } else { // typeMap 已经有一个, 进行合并
                _objectType.merge(objectType);
            }
            return objectType;
        }
        throw new IllegalStateException("Unknown node type, " + nodeName + ": " + jsonNode);
    }

    private ObjectType antiObject(String qualifiedName, ObjectNode objectNode) {
        Map<String, Type> fields = StreamSupport.stream(Spliterators.spliteratorUnknownSize(objectNode.fields(), Spliterator.ORDERED), false)
                .collect(Collectors.toMap(Map.Entry::getKey, (entry) -> abstractJson(qualifiedName, entry.getKey(), entry.getValue())));
        return new ObjectType(qualifiedName, fields);
    }

    private ArrayType antiArray(String qualifiedName, ArrayNode arrayNode) {
        Type type = StreamSupport.stream(Spliterators.spliteratorUnknownSize(arrayNode.elements(), Spliterator.ORDERED), false)
                .map((elementNode) -> abstractJson(qualifiedName, Constant.ANONYMOUS_NODE_NAME, elementNode))
                .reduce(Type::reduce)
                .orElse(EmptyElementType.getInstance());
        return new ArrayType(type);
    }

    /**
     * 获取全限定名
     */
    private String generateQualifiedName(String parentNodeName, String nodeName) {
        return parentNodeName + Constant.DELIMITER + StringUtil.toUpperCamelCase(nodeName);
    }
}
